using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using DirectShowLib;
using System.Threading;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Xml.Serialization;

namespace Lizk.DShowPlayer
{
    public class TVPlayer: Player, IDisposable
    {
        IBaseFilter tvdevice;
        IAMTVTuner tunerFilter = null;
        IAMTVAudio audio = null;
        IBaseFilter crossbar;
        TvSettings tuner;
        
        public TVPlayer(TvSettings tuner)
        {
          this.tuner = tuner;
        }

        public override void Load(Form owner, IVMRSurfaceAllocator9 allocator)
        {
            mainForm = owner;
            if (mainForm == null)
                throw new ArgumentException("A form is needed to receive messages");

                try
                {
                    graphBuilder = (IFilterGraph2)new FilterGraph();

                    AddVmr9(allocator);

                    BuildGraph();
                    SetDevice();

                    if (tvdevice == null)
                    {
                        Dispose();
                        return;
                    }

                    ChangeState(PlayerState.Loaded);
                }
                catch
                {
                    Dispose();
                    throw;
                }

        }


        public override void Load(Control targetControl)
        {
            this.targetControl = targetControl;
            mainForm = targetControl.TopLevelControl as Form;
            if (mainForm == null)
                throw new ArgumentException("The target control does not have a toplevel form");

            try
            {

                int hr = 0;
                graphBuilder = (IFilterGraph2)new FilterGraph();
                
                BuildGraph();
                SetDevice();
                noVideo = AudioOnly();
                if (!noVideo)
                {

                    resizeEvent = new EventHandler(targetControl_Resize);
                    targetControl.Resize += resizeEvent;
                    
                    // Setup the video window
                    hr = this.videoWindow.put_Owner(targetControl.Handle);
                    DsError.ThrowExceptionForHR(hr);

                    hr = this.videoWindow.put_WindowStyle(WindowStyle.Child | WindowStyle.ClipSiblings | WindowStyle.ClipChildren);
                    DsError.ThrowExceptionForHR(hr);

                    hr = this.videoWindow.SetWindowPosition(0, 0, targetControl.Width, targetControl.Height);
                    DsError.ThrowExceptionForHR(hr);
                }
                ChangeState(PlayerState.Loaded);
            }
            catch
            {
                Dispose();
                throw;
            }

        }

        private void SetDevice()
        {

            DsDevice device = null;
            object source = null;
            Guid iid = typeof(IBaseFilter).GUID;
            int hr;
            
            foreach (DsDevice ds in DsDevice.GetDevicesOfCat(FilterCategory.VideoInputDevice))
            {
                if (ds.DevicePath == tuner.Device.DevicePath)
                {
                    device = ds;
                    device.Mon.BindToObject(null, null, ref iid, out source);
                    break;
                }
                else
                {
                    ds.Dispose();
                }
            }
            if (device == null)
                return;

            if (source == null)
                return;
            tvdevice = (IBaseFilter)source;


            //Create the Capture Graph Builder
            ICaptureGraphBuilder2 captureGraphBuilder = null;
            captureGraphBuilder = (ICaptureGraphBuilder2)new CaptureGraphBuilder2();

            // Attach the filter graph to the capture graph
            hr = captureGraphBuilder.SetFiltergraph(this.graphBuilder);
            DsError.ThrowExceptionForHR(hr);

            //Add the Video input device to the graph
            hr = graphBuilder.AddFilter(tvdevice, "source filter");
            DsError.ThrowExceptionForHR(hr);



            IPin pinOut = null;
            captureGraphBuilder.FindPin(source, PinDirection.Output, null, MediaType.Audio, true, 0, out pinOut);
            graphBuilder.Render(pinOut);


            hr = captureGraphBuilder.RenderStream(PinCategory.Capture, MediaType.Video, tvdevice, null, vmr9);
            DsError.ThrowExceptionForHR(hr);


            object o;
            hr = captureGraphBuilder.FindInterface(null, null, tvdevice, typeof(IAMTVTuner).GUID, out o);
            if (hr >= 0)
            {
                tunerFilter = (IAMTVTuner)o;
                o = null;

                hr = captureGraphBuilder.FindInterface(null, null, tvdevice, typeof(IAMCrossbar).GUID, out o);
                if (hr >= 0)
                {
                    crossbar = (IBaseFilter)o;
                    o = null;

                    hr = captureGraphBuilder.FindInterface(null, null, tvdevice, typeof(IAMTVAudio).GUID, out o);
                    if (hr >= 0)
                    {
                        audio = (IAMTVAudio)o;
                        o = null;
                    }

                }
            }
            TunerInputType tunerInput;
            hr = tunerFilter.get_InputType(0, out tunerInput);
            DsError.ThrowExceptionForHR(hr);
            switch (tunerInput)
            {
                case TunerInputType.Antenna:
                    inputType = InputType.Antenna;
                    break;
                case TunerInputType.Cable:
                    inputType = InputType.Cable;
                    break;
            }

            hr = tunerFilter.get_Channel(out channel, out videoSubChannel, out audioSubChannel);
            DsError.ThrowExceptionForHR(hr);

            hr = tunerFilter.get_Mode(out tunerMode);
            DsError.ThrowExceptionForHR(hr);
        }

        AMTunerModeType tunerMode;
        AMTunerSubChannel videoSubChannel, audioSubChannel;

        private bool hasSignal;
        public bool HasSignal
        {
            get { return hasSignal; }
        }


        private int channel;
        public int Channel
        {
            get { return channel; }
        }

        public void SetChannel(int channel)
        {
            int hr = tunerFilter.put_Channel(channel, AMTunerSubChannel.Default, AMTunerSubChannel.Default);
            DsError.ThrowExceptionForHR(hr);
            
            hr = tunerFilter.get_Channel(out this.channel, out videoSubChannel, out audioSubChannel);
            DsError.ThrowExceptionForHR(hr);
            
            AMTunerSignalStrength ss;
            hr = tunerFilter.SignalPresent(out ss);
            DsError.ThrowExceptionForHR(hr);
            hasSignal = ss == AMTunerSignalStrength.SignalPresent;
        }

        public override void Play()
        {
            int hr = mediaControl.Run();
            DsError.ThrowExceptionForHR(hr);
            ChangeState(PlayerState.Playing);
        }

        private InputType inputType;
        public InputType Input
        {
            get { return inputType; }
        }

        public void GetMaxAndMinChan(out int min, out int max)
        {
            int hr = tunerFilter.ChannelMinMax(out min, out max);
            DsError.ThrowExceptionForHR(hr);
        }

        public void SetInputType(int index, InputType type)
        {
            TunerInputType tunerInput= TunerInputType.Antenna;
            int hr;
            switch (type)
            {
                case InputType.Antenna:
                    tunerInput = TunerInputType.Antenna;
                    break;
                case InputType.Cable:
                    tunerInput = TunerInputType.Cable;
                    break;
            }
            this.inputType = type;
            hr = tunerFilter.put_InputType(index, tunerInput);
            DsError.ThrowExceptionForHR(hr);
        }

        public void AutoTune()
        {
            int a;
            int hr = tunerFilter.AutoTune(channel, out a);
            DsError.ThrowExceptionForHR(hr);
        }


        public enum InputType
        {
            Antenna,
            Cable
        }

        private void targetControl_Resize(object sender, EventArgs e)
        {
            if (videoWindow != null)
            {
                int hr = this.videoWindow.SetWindowPosition(0, 0, targetControl.Width, targetControl.Height);
                DsError.ThrowExceptionForHR(hr);
            }
        }

        
        private void BuildGraph()
        {
            int hr = 0;
//            hr = graphBuilder.RenderFile(filePath, null);
            DsError.ThrowExceptionForHR(hr);

            // QueryInterface for DirectShow interfaces
            this.mediaControl = (IMediaControl)this.graphBuilder;
            this.mediaEventEx = (IMediaEventEx)this.graphBuilder;
            this.mediaSeeking = (IMediaSeeking)this.graphBuilder;
            this.mediaPosition = (IMediaPosition)this.graphBuilder;

            // Query for video interfaces, which may not be relevant for audio files
            this.videoWindow = this.graphBuilder as IVideoWindow;
            this.basicVideo = this.graphBuilder as IBasicVideo;

            // Query for audio interfaces, which may not be relevant for video-only files
            this.basicAudio = this.graphBuilder as IBasicAudio;

            if (mainForm.InvokeRequired)
            {
                mainForm.Invoke(new WaitCallback(delegate(object o)
                {
                    TVPlayer p = (TVPlayer)o;
                    // Have the graph signal event via window callbacks for performance
                    int r = p.mediaEventEx.SetNotifyWindow(p.mainForm.Handle, WMGraphNotify, new IntPtr(p.GetHashCode())); // send notifications but this fileplayer should only read notifications that it owns.
                    DsError.ThrowExceptionForHR(r);
                }), this);
            }
            else
            {
                // Have the graph signal event via window callbacks for performance
                hr = mediaEventEx.SetNotifyWindow(mainForm.Handle, WMGraphNotify, new IntPtr(this.GetHashCode())); // send notifications but this fileplayer should only read notifications that it owns.
                DsError.ThrowExceptionForHR(hr);
            }

#if DEBUG
            remoteGraph = new DsROTEntry(graphBuilder);
#endif

        }


        private bool AudioOnly()
        {
            int hr = 0;
            OABool lVisible;

            if ((this.videoWindow == null) || (this.basicVideo == null))
            {
                // Audio-only files have no video interfaces.  This might also
                // be a file whose video component uses an unknown video codec.

                return true;
            }

            hr = this.videoWindow.get_Visible(out lVisible);
            if (hr < 0)
            {
                // If this is an audio-only clip, get_Visible() won't work.
                //
                // Also, if this video is encoded with an unsupported codec,
                // we won't see any video, although the audio will work if it is
                // of a supported format.
                if (hr == unchecked((int)0x80004002)) //E_NOINTERFACE
                {
                    return true;
                }
                else
                    DsError.ThrowExceptionForHR(hr);
            }
            return false;
        }
        public override void Dispose()
        {
            int hr;

#if DEBUG
            if (remoteGraph != null)
            {
                remoteGraph.Dispose();
                remoteGraph = null;
            }
#endif


            mainForm = null;
            if (mediaEventEx != null)
            {
                hr = mediaEventEx.SetNotifyWindow(IntPtr.Zero, 0, IntPtr.Zero);
            }

            if (mediaControl != null)
            {
                hr = mediaControl.Stop();
            }

            // Relinquish ownership (IMPORTANT!) after hiding video window
            if (!noVideo && this.videoWindow != null && targetControl != null)
            {
                hr = this.videoWindow.put_Visible(OABool.False);
                DsError.ThrowExceptionForHR(hr);
                hr = this.videoWindow.put_Owner(IntPtr.Zero);
                DsError.ThrowExceptionForHR(hr);
            }

            if (targetControl != null && resizeEvent != null)
            {
                targetControl.Resize -= resizeEvent;
            }
            try
            {
                if (tunerFilter != null)
                {
                    Marshal.ReleaseComObject(tunerFilter);
                }
                if (crossbar != null)
                {
                    Marshal.ReleaseComObject(crossbar);
                }
                if (audio != null)
                {
                    Marshal.ReleaseComObject(audio);
                }
            }
            catch
            {
            }
            tunerFilter = null;
            crossbar = null;
            audio = null;

            if (vmr9 != null)
            {
                Marshal.ReleaseComObject(vmr9);
                vmr9 = null;
            }


            mediaControl = null;
            mediaSeeking = null;
            mediaPosition = null;
            mediaEventEx = null;
            videoWindow = null;
            basicVideo = null;
            basicAudio = null;


            if (graphBuilder != null)
            {
                Marshal.ReleaseComObject(graphBuilder);
                graphBuilder = null;
            }
            ChangeState(PlayerState.Disposed);
        }


        protected override bool Message(ref Message m)
        {
            switch (m.Msg)
            {
                case WMGraphNotify:
                    {
                            IntPtr p1, p2; int hr = 0;
                            EventCode code;
                            do
                            {
                                hr = mediaEventEx.GetEvent(out code, out p1, out p2, 0);
                                if (code != 0)
                                {
                                    Debug.WriteLine(code.ToString());
                                }
                                if (hr < 0)
                                {
                                    break;
                                }

                                switch (code)
                                {
                                    case EventCode.LengthChanged:

                                        break;
                                    case EventCode.Complete:
                                        mediaControl.Stop();
                                        ChangeState(PlayerState.Loaded);
                                        break;
                                }

                                hr = mediaEventEx.FreeEventParams(code, p1, p2);
                            }
                            while (hr == 0);
                        break;
                    }
            }
            return false;
        }

        public override void EnableSubtitle(int id)
        {
        }

        public override void EnableAudioTrack(int id)
        {
        }

        public int Volume
        {
            get
            {
                if (basicAudio != null)
                {
                    int i;
                    basicAudio.get_Volume(out i);

                    i *= -1;
                    float f = i;
                    
                    f /= 100;

                    return (int)(100f - f);
                }
                return 0;
            }
            set
            {
                if (basicAudio != null)
                {
                    if (value > 100) value = 100;
                    if (value < 0) value = 0;
                    int hr;
                    int t = 100 - value;
                    hr = basicAudio.put_Volume(t * -100);
                    DsError.ThrowExceptionForHR(hr);
                }
            }
        }


        protected override TimeSpan GetPosition()
        {
            return TimeSpan.Zero;
        }

        public static IEnumerable<InputDevice> CaptureDevices
        {
            get {
                List<InputDevice> devices = new List<InputDevice>();
                foreach (DsDevice ds in DsDevice.GetDevicesOfCat(FilterCategory.VideoInputDevice))
                {
                    devices.Add(new InputDevice(ds.Name, ds.DevicePath));
                    ds.Dispose();
                }
                return devices;
            }
        }

    
    }
}
